package com.pig4cloud.pig.admin.service.impl;

import cn.hutool.core.date.DateUtil;
import com.alibaba.fastjson.JSONObject;
import com.pig4cloud.pig.admin.service.HttpUtilService;
import org.apache.commons.io.IOUtils;
import org.apache.http.HttpEntity;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.cookie.Cookie;
import org.apache.http.impl.client.*;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * connectionRequestTimeout：指从连接池获取连接的超时时间（当请求并发数量大于连接池中的连接数量时，
 *                           则获取不到连接的请求会被放入 pending 队列等待，
 *                           如果超过设定的时间，则抛出 ConnectionPoolTimeoutException）。
 * connectionTimeout：指客户端和服务器建立连接的超时时间。
 *                  （当客户端和服务器在建立链接时，如果在指定时间内无法成功建立链接，则抛出 ConnectionTimeoutException）。
 * socketTimeout：指客户端从服务器读取数据的超时时间，即客户端和服务器 socket 通信的超时时间，
 *                其实这个时间是客户端两次读取数据的最长时间，如果客户端在网络抖动的情况下，每次返回部分数据，
 *                两次数据包的时间在设定时间之内，也是不会超时的。
 */
@Service
public class HttpUtilServiceImpl implements HttpUtilService {

    private static final Logger log = LoggerFactory.getLogger(HttpUtilServiceImpl.class);

    @Autowired
    private RedisTemplate<String,String> redisTemplate;

    private static final int min = 1;
    private static final int max = 10;

    private static final int DEFAULT_POOL_MAX_TOTAL = 200;
    private static final int DEFAULT_POOL_MAX_PER_ROUTE = 200;

    private static final int DEFAULT_CONNECT_TIMEOUT = 30000;
    private static final int DEFAULT_CONNECT_REQUEST_TIMEOUT = 30000;
    private static final int DEFAULT_SOCKET_TIMEOUT = 300000;

    private PoolingHttpClientConnectionManager gcm = null;

    private CloseableHttpClient httpClient = null;


    // 连接池的最大连接数
    private final int maxTotal;
    // 连接池按route配置的最大连接数
    private final int maxPerRoute;
    // tcp connect的超时时间
    private final int connectTimeout;
    // 从连接池获取连接的超时时间
    private final int connectRequestTimeout;
    // tcp io的读写超时时间
    private final int socketTimeout;

    //构造方法
    public HttpUtilServiceImpl() {
        this(HttpUtilServiceImpl.DEFAULT_POOL_MAX_TOTAL, HttpUtilServiceImpl.DEFAULT_POOL_MAX_PER_ROUTE,
                HttpUtilServiceImpl.DEFAULT_CONNECT_TIMEOUT, HttpUtilServiceImpl.DEFAULT_CONNECT_REQUEST_TIMEOUT, HttpUtilServiceImpl.DEFAULT_SOCKET_TIMEOUT);
    }

    public HttpUtilServiceImpl(int maxTotal, int maxPerRoute, int connectTimeout, int connectRequestTimeout, int socketTimeout) {

        this.maxTotal = maxTotal;
        this.maxPerRoute = maxPerRoute;
        this.connectTimeout = connectTimeout;
        this.connectRequestTimeout = connectRequestTimeout;
        this.socketTimeout = socketTimeout;

        Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
                .register("http", PlainConnectionSocketFactory.getSocketFactory())
                .register("https", SSLConnectionSocketFactory.getSocketFactory())
                .build();

        this.gcm = new PoolingHttpClientConnectionManager(registry);
        this.gcm.setMaxTotal(this.maxTotal);
        this.gcm.setDefaultMaxPerRoute(this.maxPerRoute);

        RequestConfig requestConfig = RequestConfig.custom()
                .setConnectTimeout(this.connectTimeout)                   // 设置连接超时
                .setSocketTimeout(this.socketTimeout)                     // 设置读取超时
                .setConnectionRequestTimeout(this.connectRequestTimeout)  // 设置从连接池获取连接实例的超时
                .build();

        HttpClientBuilder httpClientBuilder = HttpClients.custom();
        this.httpClient = httpClientBuilder
                .setConnectionManager(this.gcm)
                .setConnectionManagerShared(true)
                .setDefaultRequestConfig(requestConfig)
                .build();


    }

    public String getRequest(int i,Class clazz,String url,String charset) {
        //validateScore(url);
        CloseableHttpResponse response = null;
        try {
            HttpGet httpget = new HttpGet(url);
            httpget.setHeader("User-Agent",getRandomAgent());
            response = httpClient.execute(httpget);
            int statusCode = response.getStatusLine().getStatusCode();
            log.info("### [{}]开始执行任务，响应状态为[{}]",url,statusCode);
            if (response == null) {
                //deductPoints(i,url);
                addException(url,clazz,charset,new RuntimeException("请问网址响应为空"));
            }
            String res = null;
            HttpEntity entityRes = response.getEntity();
            if (entityRes != null) {
                res =  EntityUtils.toString(entityRes, charset);
            }else{
                //deductPoints(i,url);
                addException(url,clazz,charset,new RuntimeException("响应码为["+statusCode+"]，获取响应体失败"));
            }
            return res;
        } catch (Throwable e) {
            //deductPoints(i,url);
            addException(url,clazz,charset,new RuntimeException("read time out"));
            throw new RuntimeException(e);
        } finally {
            IOUtils.closeQuietly(response);
        }
    }

    public String getRequestFallback(int i,Class clazz,String url,String charset) {
        log.info("对[{}]进行限流",url);
        return url;
    }

    private void addException(String url,Class clazz,String charset,Exception e){
        JSONObject json = new JSONObject();
        json.put("class",clazz);
        json.put("charset",charset);
        json.put("createTime", DateUtil.now());
        json.put("error",e.getMessage());
        //redisTemplate.opsForHash().put(CommonConstant.CACHE_TIMEOUT_JOB,url,json.toJSONString());
        throw new RuntimeException(e.getMessage());
    }

    @Override
    public String gethtml(String url) {
        org.apache.http.client.CookieStore cookieStore = new BasicCookieStore();
        CloseableHttpClient httpclient = HttpClients.createDefault();
        String string = null;
        HttpGet httpget = new HttpGet(url);
        RequestConfig config = RequestConfig.custom().setConnectTimeout(10 * 1000).setSocketTimeout(20 * 1000).build();
        httpget.setConfig(config);
        httpget.setHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36");
        CloseableHttpResponse response = null;
        try {
            response = httpclient.execute(httpget);
            HttpEntity entity = response.getEntity();
            Integer statu = response.getStatusLine().getStatusCode();
            List<Cookie> cookies = null;
            if (entity != null) {
                string = EntityUtils.toString(entity, "UTF-8");
                EntityUtils.consume(response.getEntity());
                cookies = cookieStore.getCookies();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                response.close();
                httpclient.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return string;
    }


    @Override
    public void doBefore(String webUrl){
		try {
			URL url = new URL(webUrl);
			HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
			urlConnection.setRequestMethod("HEAD");
			urlConnection.connect();
			if(urlConnection.getResponseCode() != 200){
				throw new RuntimeException("###【"+webUrl+"】网站访问失败");
			}
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}

    /**
     * 随机一个Agent
     * @return
     */
    public String getRandomAgent() {
        List<String> RandomAgent = new ArrayList<String>();
        RandomAgent.add("Mozilla/5.0 (Windows NT 6.1; WOW64; rv:34.0) Gecko/20100101 Firefox/34.0");
        RandomAgent.add("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36");
        RandomAgent.add("Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.71 Safari/537.36");
        RandomAgent.add("Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.64 Safari/537.11");
        RandomAgent.add("Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.133 Safari/534.16");
        int sign = (int)(0+Math.random()*(5));
        return RandomAgent.get(sign);
    }

    /*public void validateScore(String url){
        Object score = redisTemplate.opsForHash().get(CommonConstant.Cache_Website_Score, url);
        if(score != null){
            Integer s = Integer.valueOf(score.toString());
            if(s > 4){
                throw new RuntimeException("### ["+url+"]被频繁访问，请求被退回");
            }else{
                int random = ThreadLocalRandom.current().nextInt(min, max) % 3;
                if(random != 0){
                    throw new RuntimeException("### ["+url+"]已达到访问次数限制，请求被退回");
                }
            }
        }
    }*/

    /*public void deductPoints(int i,String url){
        AtomicInteger atomicInteger = new AtomicInteger(0);
        if(i == 1 ){
            atomicInteger.getAndIncrement();
        }else if(i == 2){
            atomicInteger.getAndAdd(2);
        }else {
            atomicInteger.getAndAdd(4);
        }
        redisTemplate.opsForHash().increment(CommonConstant.Cache_Website_Score,url,atomicInteger.get());
    }*/

}
